package com.gitee.fanx.rpc.client;

import com.fasterxml.jackson.databind.JavaType;
import com.gitee.fanx.rpc.JavaTypeResolver;
import com.gitee.fanx.rpc.RpcCodec;
import com.gitee.fanx.rpc.RpcConst;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.*;

/**
 * @author fanhang
 */
public class RpcClientInvoker implements InvocationHandler {
	private final Logger logger = LoggerFactory.getLogger(RpcClientInvoker.class);
	private static final Object[] EMPTY_PARAMS = new Object[]{};
	private final RestTemplate restTemplate;
	private final Map<Method, URI> methodUriMap = new HashMap<>(8);
	private final Map<Method, JavaType> methodReturnTypeMap = new HashMap<>(8);

	public RpcClientInvoker(Class<?> apiClass, RestTemplate restTemplate, String serviceName) {
		this.restTemplate = restTemplate;
		for (Method method : apiClass.getMethods()) {
			if (!Modifier.isAbstract(method.getModifiers())) {
				continue;
			}
			methodUriMap.put(method, RpcConst.createUri(serviceName, apiClass, method.getName()));
			methodReturnTypeMap.put(method, JavaTypeResolver.methodReturnType(method));
		}
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if (method.isDefault()) {
			// 接口 default 方法
			return InvocationHandler.invokeDefault(proxy, method, args);
		}
		URI uri = Optional.ofNullable(methodUriMap.get(method)).orElseThrow(() -> new UnsupportedOperationException("Method [" + method + "] uri missing"));
		JavaType returnType = Optional.ofNullable(methodReturnTypeMap.get(method)).orElseThrow(() -> new UnsupportedOperationException("Method [" + method + "] returnType missing"));
		Object[] params = Optional.ofNullable(args).orElse(EMPTY_PARAMS);
		logger.debug("RPC request: {}, {}", uri, params);
		ResponseEntity<String> response = restTemplate.postForEntity(uri, params, String.class);
		if (Void.TYPE.equals(returnType.getRawClass())) {
			return null;
		}
		return RpcCodec.decode(response.getBody(), returnType);
	}
}
